""" Defines the resource management system.

    DEPRICATION NOTICE: The use of the GRM system is now deprecated in
    favor of the `sbak.game.Game.resources` member. Objects 

    A resource manager (ResourceMansger object) is used to dynamically
    load resources as needed, but prevents multiple copies of
    a resource from being loaded unnecessarily.
    
    The ResourceManager class supports many of the methods of dict,
    including __getitem__, __setitem__, get, __iter__, __contains__,
    and iteritems. It also defines a number of other methods useful for
    resource management.
    
    When getting the value associated with a key, the key is
    interpreted as a filename and the resource contained in the given
    file is loaded as the key's value. If the filename has already been
    loaded and at least one strong reference to the value already
    exists, then that value is returned rather than loading the file
    again. This is true for both __getitem__ and get. However, the
    `get` method allows a second argument, `tag`, to specify a tag
    (a string that indicates type) to use in order to determine which
    loader should load the file.
    
    Before a file can be loaded, a loader must be registered for the
    file's tag. This is done using the `set_tag_loader` method.
    There may be one unique file loader registered for each tag, but
    multiple extensions may be registered with each tag.
    
    In order for the ResourceManager to be able to determine the
    correct tag for a file, the file extension must be associated
    with a tag string. This can be done using the `set_ext_tag` method.
    If the file has no extension or if you want to force a certain
    loader to be used, you must use the `get` method so that its second
    argument can be utilized to specify a tag. (Note that if a file is
    already loaded, neither the extension nor tag will matter, as no
    loading is actually done.)
    
    In case it isn't obvious by now, the ResourceManager class
    considers the *type* of a file and the *extension* of a file to be
    two seperate things. This is because several different file
    extensions can refer to the same type of file. For example, the
    ".png", ".bmp", and ".jpg" extensions are different, but all refer
    to image files.
    
    When a file is loaded from a given filename, the absolute path for
    that file is stored internally so that if the same file is referred
    to by using a different name, it won't be loaded again. However,
    when iterating over a ResourceManager object, only the filename
    that was used to originally load the resource will be used.
    
    NOTE: At some places in this module the resource manager class'
    instances are refered to in abbreviated form as "mgr".
"""

import os
import warnings
from weakref import WeakValueDictionary, WeakKeyDictionary

# Make sure some variant of JSON is available for resource management...
try:
    import json
except ImportError:
    try:
        import simplejson as json
    except ImportError:
        import warnings
        warnings.warn(
    """
    
    JSON support for Python doesn't appear to be installed on this
    system. You must install JSON support for Python before you can use
    all of ScrollBack's resource saving and loading features. Go to
    http://pypi.python.org/pypi/simplejson/ to download and install
    JSON support for Python versions prior to 2.6.
    ScrollBack will now run without support for saving or loading
    resources.
    """)

from pygame import Surface

import sbak
from sbak import (
    debug,
    error,
)

# All recognized file extensions
EXTS_SURFACE = (
    'jpg',
    'jpeg',
    'png',
    'gif',
    'bmp',
    'pcx',
    'tga',
    'tif',
    'lbm',
    'pbm',
    'pgm',
    'ppm',
    'xpm'
)
EXTS_IMAGE = (
    'image',
    'rie'
)
EXTS_WORLD = (
    'world',
    'rwd'
)
EXTS_TILELIST = (
    'tilelist',
    'tiles',
    'rtt'
)

__all__ = (
    'AbstractResource'
    'ResourceManager',
    
    'init',
    'set_mgr',
    'get_mgr'
)


# ~~~~~~~ CLASSES ~~~~~~~~

dict_ = dict
class RepresentableM(sbak.base.ObjectM):
    """ Metaclass for Representable. """
    
    def __init__(cls, name, bases, dict):
        super(RepresentableM, cls).__init__(name, bases, dict)
        
        reppers = {}
        unreppers = {}
        
        # Find all rep and unrep methods,
        for name,meth in dict.iteritems():
            # If this member is a representer, add rep data for it.
            if name.startswith("_rep_") and name[5:] not in cls.no_rep:
                reppers[name[5:]] = meth
            
            # If this member is an unrepresenter, add rep data for it.
            elif name.startswith("_unrep_") and name[7:] not in cls.no_unrep:
                unreppers[name[7:]] = meth
        
        # Give the rep tables to the class.
        cls._reppers = reppers
        cls._unreppers = unreppers

class Representable(sbak.base.Object):
    """ Abstract base class for JSON-representable objects.
    
        This class makes special use of methods that have the prefixes
        "_rep_" or "_unrep_":
        
        * Methods named like "_rep_x" will be used to get a
          JSON-representable version of the member `x` in the
          object.
        
        * Methods named like "_unrep_x" will be used to create an
          object that will be passed to the constructor as the
          argument `x` when creating an instance from JSON data.
          
        _rep_ and _unrep_ methods be defined with the following form,
        with things in [brackets] being optional:
        
        >>> def _rep_x(self [, m [, n]):
        >>>     ''' Returns a JSON representation of member `x`.
        >>>
        >>>         m:  A resource manager that may be used to aid
        >>>             representation of subobjects of `x` if needed.
        >>>
        >>>         n:  A flag that indicates whether or not the
        >>>             object or subobjects of the object being
        >>>             represented should be represented as None
        >>>             (JSON 'null') when they cannot otherwise
        >>>             be represented.
        >>>     '''
        >>>     return rep_of_x
        >>>
        >>> def _unrep_x(self, v [, m [, d]]):
        >>>     ''' Returns an object based on a rep. of member `x`
        >>>
        >>>         v:  The representation of the value.
        >>>
        >>>         [m:  A resource manager that may be used to aid
        >>>             in unrepresenting child objects.
        >>>
        >>>          [d:  Other represented data (in case it is
        >>>               needed).]
        >>>         ]
        >>>
        >>>     '''
        >>>     return x_of_rep
        
        You do not have to put the `m` or `d` arguments into a
        method's signature if the method doesn't need them. If the
        arguments are not present in the method definition, the
        method will not be given those arguments.
        
        If the special-prefixed methods approach does not suit your
        needs or if you find yourself making heavy use of the `d` and
        `m` arguments, you can override the to_json and from_json
        methods for more complete control over how the representations
        are created.
        
        If you create subclasses of classes that are based on
        Representable, they will use the same methods as their
        superclass to rep/unrep the same corresponding members. If you
        don't want particular members of the subclass to be represented,
        even though representers exist for them, you can exclude them
        using the `no_rep` and `no_unrep` class members:
        
        >>> class Foo(Representable):
        >>>
        >>>     def __init__(self, foo):
        >>>         self.foo = foo
        >>>
        >>>     _rep_foo = lambda self: int(foo)
        >>>
        >>> class Foo100(Foo):
        >>>
        >>>     no_rep = ['foo']
        >>>     no_unrep = ['foo']
        >>>
        >>>     def __init__(self):
        >>>         self.foo = 100
        >>>
        
        It is permissible to create a representer method without an
        unrepresenter. In this case, data in the rep. will be passed
        to the constructor unaltered. It is also permissible to have
        an unrepresenter without a representer because members missing
        from the given data will simply not get passed to the
        constructor.
        
        This class also makes use of a special class attribute, `tag`.
        The purpose of `tag` is to help complex un-representer chains
        to determine how the rep. is to be unrepped.
        
        The to_json method only includes the `tag` attribute in a rep
        of an object if the tag exists on that object or its class; the
        Representer class itself does not actually have the `tag`
        attribute, so it is up to subclasses to implement it. Also,
        The from_json method will not pass the `tag` attriubte on to
        the constructor when creating new instances.
    """
    
    __metaclass__ = RepresentableM
    
    no_rep = ()
    no_unrep = ()
    
    @classmethod
    def from_json(cls, data, resmgr=None):
        """ Create an object from a JSON representation.
        
            data:   JSON representation of the object. A copy of this
                    mapping will be expanded into the arguments for
                    the constructor with values for which a _unrep_
                    method exists replaced by the values returned from
                    those methods.
            
            resmgr: A resource manager to use to un-rep subobjects
                    in the data. If None the first of the following
                    resource managers that is found will be used:
                        1.) sbak.game.default.resources
                        2.) sbak.resman.get_mgr()
            
            Returns a representable.
        """
        # If no resource manager was given, try the chain
        if resmgr is None:
            try:
                # FIXME: This is bad coding, but needed for using game.default
                from sbak import game
                resmgr = game.default.resources
            except AttributeError:
                    resmgr = get_mgr()
        
        # Get arguments for the constructor from the data.
        args = dict(data)
        for name,unrep in cls._unreppers.iteritems():
            
            # Skip values that are not present in both the data and the
            # unrepper table
            if name not in data:
                continue
            
            # Try different calling schemes on the rep. method until one works.
            # (or until all allowed alternatives are exhausted.)
            try:
                args[name] = unrep(self, data[name], resmgr, data)
            except TypeError:
                try:
                    args[name] = unrep(self, data[name], resmgr)
                except TypeError:
                    args[name] = unrep(self, data[name])
        
        # Remove the tag from the arguments.
        if "tag" in args:
            del args["tag"]
        
        # Create a new object from the arguments.
        obj = cls(**args)
        
        return obj
    
    def to_json(self, resmgr=None, usenone=False):
        """ Create a JSON representation from the object.
        
            resmgr:     A resource manager to use to create
                        representations of sub-objects. If None the
                        first of the following resource managers that
                        is found will be used:
                        1.) self.game.resources
                        2.) sbak.game.default.resources
                        3.) sbak.resman.get_mgr()
                        
            usenone:    A flag indicating whether or not objects that
                        cannot be represented should be represented as
                        None. If True, None will be used for such
                        objects. If False, sbak.error.RepresentError
                        will be raised.
            
            NOTE: The trial hierarchy for determining the resmgr
            is temporary and will probably be removed soon. Don't get
            used to it!
        """
        # If no resource manager was given, try the chain
        if resmgr is None:
            try:
                resmgr = self.game.resources
            except AttributeError:
                try:
                    resmgr = game.default.resources
                except AttributeError:
                        resmgr = get_mgr()
        
        # Empty representation.
        data = {}
        
        # Add tag to the data if tag is available.
        if hasattr(self, "tag"):
            data["tag"] = self.tag
        
        # Represent attributes that are marked as representable.
        for name,rep in self._reppers.iteritems():
            # Try different calling conventions until one works, or until there
            # are no other options.
            try:
                data[name] = rep(self, resmgr, usenone)
            except TypeError:
                try:
                    data[name] = rep(self, resmgr)
                except TypeError:
                    data[name] = rep(self)
        
        return data

class Resource(Representable):
    """ Base class for all resource types.
        
        In addition to being representable, resource classes also have
        save() and load() methods for saving and loading resource data
        from files.
    """
    
    @classmethod
    def load(cls, src, resmgr=None):
        """ Loads JSON data from a file and decodes it into a resource.
        
            src:        A file name or file-like object from which to
                        load the JSON data.
            
            resmgr:     A resource manager to use to load dependencies.
                        If None, the global resource manager will be
                        used.
            
            This method does not normally need to be overridden since
            the real work is in the JSON decoder method for subclasses
            and not in the raw data loading. However, should a subclass
            need to load data from some other format besides ordinary
            JSON, this may need to be overridden.
            
            Returns a resource.
        """
        # Determine the correct way to use `src`.
        close = False
        try:
            if isinstance(src, basestring):
                src = open(src)
                close = True
                
            try:
                # Load the JSON data from the file.
                obj = json.load(src)
            
            except ValueError:
                err = '%s loader tried to load non-JSON data from %s. ' % (
                        cls.tag if hasattr(cls, 'tag') else "A resource",
                        '"' + src + '"' if isinstance(src, basestring) else src
                    )
                raise error.FormatError(err)
                
        # Close the file if necessary.
        finally:
            if close:
                src.close()
        
        # Turn the JSON object into a resource object.
        obj =  cls.from_json(obj, resmgr)
        
        return obj
    
    def save(self, dest, resmgr=None, usenone=False):
        """ Save a JSON representation of the resource to a file.
        
            dest:       Destination file. May be an open file-like
                        object or a file name.
            
            resmgr:     A resource manager to use to convert referenced
                        objects to JSON or filenames. If None, the
                        global resource manager is used.
            
            usenone:    If true, referenced resources that cannot be
                        represented as JSON or as filenames will be
                        represented as None (JSON 'null'). If false
                        (default), sbak.error.RepresentError will be
                        raised when such an object is encountered.
            
            This method does not normally need to be overridden because
            most of the work is performed by AbstractResource.to_json.
            However, should a subclass need to save its data using some
            format other than JSON, overriding may be necessary.
            
            No return value.
        """
        # Encode myself
        obj = self.to_json(resmgr, usenone)
        
        # Save encoded self to file.
        try:
            # Open dest for writing,
            close = False
            if isinstance(dest, basestring):
                dest = open(dest, 'w')
                close = True
            
            # and save encoded self to it.
            json.dump(obj, dest)
            
        # Make sure the file gets closed.
        finally:
            if close:
                dest.close()

class ResourceManager(sbak.base.Object):
    """ Manages resource files and resource objects.
    
        See module documentation for usage details.
    """
    def __init__(self):
        self._srcres = WeakValueDictionary() # Filename -> Res. Obj.
        self._ressrc = WeakKeyDictionary() # Res. Obj -> Filename
        self._cwd = os.path.curdir # Default cwd
        self._metadata = {} # Filename -> Metadata Dict.
        self._exttag = {} # Extension -> Tag
        self._tagloader = {} # Tag -> Loader Func.
        self._tagdir = {} # Tag -> Directory
    
    def set_basics(self):
        """ Registers exts., tags, and loaders for sbak file types.
        
            If you create a manager and want it to be able to handle at
            least the basic types of resource file that ScrollBack
            implements, it is easiest to just call this method
            immediately after the manager is created rather than
            excplicitly register each extension, tag, and loader.
        """
        # Because these modules typically require the use of resman, they
        # must be imported from within get_basics, which is the only place
        # that resman needs them.
        import image, world, tilelist
        
        self.set_ext_tag(EXTS_SURFACE, 'surface')
        self.set_ext_tag(EXTS_IMAGE, 'anyimage')
        self.set_ext_tag(EXTS_WORLD, 'world')
        self.set_ext_tag(EXTS_TILELIST, 'tilelist')
        
        self.set_tag_loader('surface', image.load_surface)
        self.set_tag_loader('anyimage', image.load)
        self.set_tag_loader('image', image.Image.load)
        self.set_tag_loader('animation', image.Animation.load)
        self.set_tag_loader('composite', image.Composite.load)
        self.set_tag_loader('world', world.World.load)
        self.set_tag_loader('tilelist', tilelist.TileList.load)
    
    def set_ext_tag(self,*vargs,**kwargs):
        """ Associate one or more file extensions with one or more tags.
        
            There are four calling conventions:
        
            # Two strings:
            set_ext_tag(ext, tag)
            
            # Dictionary:
            set_ext_tag({ext1:tag1, ext2:tag2, ...})
            
            # Sequence and string:
            set_ext_tag([ext1, ext2, ...], tag)
            
            # Keyword arguments:
            set_ext_tag(ext1=tag1, ext2=tid2, ...)
        
            
            In any of the above cases, the following arguments are
            used:
            
            ext:    A file extension to associate with a type ID.
                    (Do not include the initial '.' character.)
            tag:    The tag to associate with it.
            
            Both values are considered case-insensive, and leading and
            trailing whitespace is stripped.
        """
        # Prevent empty calls.
        if not vargs and not kwargs:
            raise TypeError("set_ext_tag() requires at least two arguments,"
                            "got nothing.")
        
        # Temporary mapping
        exttag = {}
        
        # Convention 1: set_ext_tag(ext, tag)
        if isinstance(vargs[0],str) and isinstance(vargs[1],str):
            exttag[vargs[0]] = vargs[1]
        
        # Convention 2: set_ext_tag({ext:tag, ext:tag, ...})
        elif hasattr(vargs[0], 'iteritems'):
            exttag = vargs[0]
        
        # Convention 3: set_ext_tag([ext, ...], tag)
        elif hasattr(vargs[0], '__iter__') and isinstance(vargs[1], str):
            for e in vargs[0]:
                exttag[e] = vargs[1]
        
        # Convention 4: set_ext_tag(ext=tag, ext=tag, ...)
        elif kwargs:
            exttag = kwargs
        
        # Unknown convention
        else:
            raise TypeError("Unknown calling convention used.")
        
        # Process the extension->tag mapping
        for ext,tag in exttag.iteritems():
            if not isinstance(ext,str):
                raise TypeError("All extensions must be strings; got %s"
                                %type(ext))
            if not isinstance(tag,str):
                raise TypeError("All tags must be strings; got %s"
                                %type(tag))
            
            # Case-insensitve, whitespace padding ignored.
            ext = ext.strip().lower()
            tag = tag.strip().lower()
            
            self._exttag[ext] = tag
    
    def remove_ext_tag(self,*vargs):
        """ Disassociates one or more file extensions from their tag.
        
            remove_ext_tag(ext, ext, ...)
            remove_ext_tag([ext, ext, ...], [ext, ext, ...], ...)
        
            Pass one or more extensions or a sequence of extensions.
            All are considered case insensitive and leading and
            trailing whitespace is stripped.
            
            Extensions which do not have an association will cause a
            ValueError.
        """
        for ext in vargs:
            if isinstance(ext,str):
                ext = ext.strip().lower()
                if ext not in self._exttag:
                    raise ValueError('Extension "%s" is unassociated.'%ext)
                del self._exttag[ext]
            elif hasattr(ext,'__iter__'):
                for ext in ext:
                    ext = ext.strip().lower()
                    if ext not in self._exttag:
                        raise ValueError('Extension "%s" is unassociated.'%ext)
                    del self._exttag[ext]
    
    def set_tag_loader(self,tag,loader):
        """ Creates or replaces a new loader for a tag.
        
            tag:    Tag to associate with the loader. Case-
                    insensitive, leading and trailing whitespace is
                    ignored.
                    
            loader: Loader to associate with the tag.
            
            The loader is basically a factory function that returns
            some type of resoruce object and accepts at least two
            positional arguments:
            
            src:    A filename, either local or absolute. Special
                    symbols will be translated before they get to the
                    loader.
            
            resmgr: The ResourceManager object. The loader should use
                    this object if it needs to load other resources
                    recursively. If the given object is None, the
                    loader should use the global resource manager
                    instead.
            
            The factory may also accept "metadata" which may be given
            as keyword arguments. (See the `set_metadata` method for
            details.)
        """
        if not isinstance(tag,basestring):
            raise TypeError("tag must be str, not %s."%type(tag))
        tag = tag.strip().lower()
        self._tagloader[tag] = loader
    
    def remove_tag_loader(self,tag):
        """ Removes a loader for a tag.
        
            tag:    Tag to dissociate. Case-insensitive, leading
                    and trailing whitespace is ignored.
        """
        tag = tag.strip().lower()
        del self._tagloader[tag]
    
    def get_cwd(self):
        """ Return the manager's current working directory.
        
            This is not the current directory of the entire program.
            This "CWD" is only used when a colon (:) is used at the
            beginning of a filename passed to get().
            By default the cwd is the currentdir symbol (ie. '.' on
            Windows and Unix, etc.)
        """
        return self._cwd
    
    def set_cwd(self,cwd=os.path.curdir):
        """ Set the manager's current working directory.
            See the note at get_cwd().
        """
        self._cwd = cwd
    
    def get(self,src,tag=None):
        """ Loads the file with the given name.
        
            src:    Name of file to load a resource from, or the name
                    of a file that was used to load a resource that is
                    already loaded.
            
            tag:    Optional tag to force. (This will determine
                    the loader, but will not change the meaning
                    of the '$' symbol.)
        
            A colon (:) at the beginning of
            the filename will be interpreted as the manager's cwd.
            
            A dollar sign ($) at the beginning will be interpreted as
            the directory associated with the tag associated with
            the extension of the filename.
            
            The absolute filename of the resource is stored internally.
            If a string that equates to the same absolute filename is
            given (after special symbols are translated) the existing
            resource object is used instead.
            
            The case of the extension on the filename is ignored when
            used to determine the tag, but preserved when given to the
            loader.
            
            Attempting to load a file with an extension that is not
            registered with a tag will raise
            sbak.error.ResourceError.
            
            Attempting to load a file that does not exist may result in
            an error, but the error depends on the loader. Usually it
            will be IOError or pygame.error.
            
            If a loader returns an object that is already loaded or
            None, a ResourceError will occur. (None is used to
            represent the absence of a resource, so it is not a valid
            resource itself.)
            
            The type of the returned value depends on the loader.
        """
        # Interpret special symbols
        truesrc = self.truepath(src)
        
        # Get absolute path to the filename.
        abssrc = os.path.abspath(truesrc)
        
        # If the resource is already loaded use the existing object.
        if abssrc in self._srcres:
            return self._srcres[abssrc]
        
        # If the resource is NOT already loaded, load it.
        else:
            # If a tag was given, use it.
            if tag:
                tag = tag.strip().lower()
            # If no tag is given, determine one from the extension.
            else:
                # Get the extension
                root, ext = os.path.splitext(truesrc)
                ext = ext[1:].strip().lower()
                try:
                    tag = self._exttag[ext]
                except KeyError:
                    raise error.ResourceError(
                        "No tag associated with extension '%s'."%ext)
            
            # Get the metadata
            md = self._metadata[abssrc] if abssrc in self._metadata else {}
            try:
                loader = self._tagloader[tag]
            except KeyError:
                raise error.ResourceError(
                    "No loader associated with tag '%s'."%tag)
            
            # Load the resource
            r = loader(truesrc, self, **md)
            
            # Don't allow None to be returned.
            if r is None:
                raise error.ResourceError(
                    "loader for '%s' returned None with path %s"%(tag,truesrc))
            
            # Don't allow existing resources objects to be loaded.
            if r in self._ressrc:
                raise error.ResourceError("loader for '%s' returned "
                    "already-loaded object %s when given path %s"
                    %(tag,r,truesrc))
            
            self._srcres[abssrc] = r
            self._ressrc[r] = src
            
            return r
    
    __getitem__ = get
    
    def add(self,src,res):
        """ Artificially add a resource to the manager.
        
            src:    A string to associate with the resource. The
                    manager will use this as the filename of the
                    resource.
            
            res:    A resource object.
            
            This will tell the resource manager that the given resource
            object has been loaded from the given filename, even if it
            was not, and even if the file with the given name doesn't
            actually exist.
            
            This can be used in situations where a resource is created
            in memory at run time and is, therefore, not associated
            with any specific file, but must still be accessed via some
            handler string.
            
            Notes:
            
            1.) Make sure that there is at least one strong reference
                to the resource before adding it, otherwise the manager
                will forget about it as soon as it is added.
            
            2.) Adding filenames or resources that are already in use
                is NOT allowed. This is to prevent the manager from
                losing track of resources that were actually loaded
                from a particular filename. Attemting to add a file
                name that is already in use will result in a
                ValueError.
                
            3.) Also, None is not an allowed resource. Attempting to
                use None will cause a TypeError.
        """
        # Don't allow None as a resource.
        if res is None:
            raise TypeError("None is not a valid resource.")
        
        # Interpret special symbols
        truesrc = self.truepath(src)
        
        # Get absolute path to the filename.
        abssrc = os.path.abspath(truesrc)
        
        # Don't allow existing filenames.
        if abssrc in self._srcres:
            raise ValueError("Filename '%s' already in use as '%s'."
                             %(src,abssrc))
        
        # Don't allow existing resources.
        if res in self._ressrc:
            raise ValueError("Resource %s already loaded using path '%s'"
                             %(res,self._ressrc[res]))
        
        # Put the resource and filename into the manager.
        self._srcres[abssrc] = res
        self._ressrc[res] = src
    
    __setitem__ = add
    
    def __iter__(self):
        return self._ressrc.values().iter()
    
    def __contains__(self,val):
        truesrc = self.truepath(val)
        abssrc = os.path.abspath(truesrc)
        return self._srcres.__contains__(val)
    
    def iteritems(self):
        return dict((self._ressrc[r], r)
                    for r in self._srcres.values()).iteritems()
    
    def keys(self):
        return self._srcres.keys()
    
    def values(self):
        return self._srcres.values()
    
    def get_src(self, res):
        """ Returns the filename for the given resource.
        
            res:    A resource object that was loaded with this
                    resource manager.
        
            If a resource was loaded with this resource manager,
            the filename that was used to load the resource can be
            determined by passing the resource object to this method.
            
            The exact value that was used to first load the resource
            will be used, whether it was a relative path or absolute
            path, and whether or not any special symbols were used.
            Even if a resource was loaded using different filenames
            at different times (i.e. different paths leading up to the
            actual file) only the first one used is returned.
            
            Raises TypeError if None is given.
            
            Raises ValueError if the resource given was not loaded with
            the resource manager.
        """
        if res is None:
            raise TypeError("None is not a valid resource.")
        try:
            return self._ressrc[res]
        except KeyError:
            raise ValueError("Resource %s was not loaded with this "
                             "resource manager."%res)
    
    def set_metadata(self, src, **md):
        """ Set metadata values for filenames.
        
            If a filename has metadata associated with it the metadata
            will be passed to the loader by keyword when the filename
            is loaded. Do not set metadata for a filename that a loader
            is not expecting because it will probably cause a
            TypeError. (Note, however, that the built-in loaders ignore
            unknown metadata so it is save to set whatever you want for
            files that are loaded with them.)
        """
        # Interpret special symbols
        src = self.truepath(src)
        
        # Get absolute path to the filename.
        abssrc = os.path.abspath(src)
        
        # If there is no metadata for this filename, add it
        if abssrc not in self._metadata:
            self._metadata[abssrc] = {}
        for k,v in md.iteritems():
            self._metadata[abssrc][k] = v
    
    def set_tag_dir(self,tag,dir=''):
        """ Set the directory for files with the given tag.
        
            tag:    A tag. Case insensitive, leading and trailing
                    whitespace is ignored.
            dir:    A file directory. Defaults to '' (empty string).
            
            After this is set, using the '$' symbol at the beginning of
            a filename will be interpreted as the directory associated
            with the tag of the file. Filename case is not used
            to determine this.
        """
        if not isinstance(tag,str):    
            raise TypeError("tag must be str, got %s"%tag)
        self._tagdir[tag.strip().lower()] = str(dir)
    
    def truepath(self,src):
        """ Returns the filename with special symbols translated.
        
            The ResourceManager class supports two "special symbols"
            at the beginning of a filename. If present they are
            translated into something else:
            
            * The colon (:) is translated to the CWD of the manager.
              This is not the same as the CWD of the program; it is
              unique to the manager.
              
            * The dollar sign ($) is translated to a directory that is
              associated with the tag of the filename. This
              directory can be set using set_tag_dir().
              
            You can also specify both in the form ":$" to mean a
            combination of the two. However, the oposite, "$:" will
            not work, and will raise a ValueError.
            
            If there is no directory associated with a tag then
            the '$' will translate to an empty string for filenames of
            that type.
        """
        root, ext = os.path.splitext(src)
        ext = ext[1:].lower()
        tag = self._exttag[ext] if ext in self._exttag else None
        dir = self._tagdir[tag] if tag in self._tagdir else ''
        if src.startswith(':$'):
            return os.path.join(self._cwd, dir, src[2:])
        elif src.startswith('$:'):
            raise ValueError("Filename may not begin with '$:'.")
        elif src.startswith(':'):
            return os.path.join(self._cwd, src[1:])
        elif src.startswith('$'):
            return os.path.join(dir, src[1:])
        else:
            return src
    
    def res_to_json(self, res, usenone=False, usesrc=False):
        """ Attempt to represent a resource as a JSON object.
        
            res:        The resource to represent.
            
            usenone:    If true, objects that cannot be represented
                        either as a JSON object or as a filename will
                        be represented as None (including the top-level
                        object.) Otherwise,
                        sbak.error.RepresentError occurs when such an
                        object is encountered.
            
            usesrc:     If true, the method will return the filename
                        of the given res if it is available, or else the
                        usual JSON representation. If false (default),
                        the JSON representation is always returned.
            
            This method relies on the existence of a `to_json` method
            implemented on objects being represented. That method must
            be of the following form:
            
            class RepresentableObject:
                ...
                def to_json (self, resmgr, usenone):
                    ...
                    return json_representation
                ...
                
            where `resmgr` is the manager object calling the method,
            (should be used to call resmgr.res_to_json() recursively
            on sub-objects of the resource) and `usenone` has the same
            meaning as and is passed along from the res_to_json being
            called.
            
            Unlike many of ResourceManager's methods, this method WILL
            allow for None to be used as a resource. In the case of
            None, None is the object returned because None is its own
            JSON representation object. The rational for this is that
            you aren't really representing None as a resource here;
            rather, you are representing an "empty space" where a
            resource could go if one was present. However, one is not.
            
            Returns a filename, None, or the value returned by the
            to_json method of the represented object.
        """
        resjson = None
        
        try:
            if usesrc:
                resjson = self.get_src(res)
            else:
                raise ValueError
        except ValueError:
            try:
                resjson = res.to_json(resmgr, usenone)
            except AttributeError:
                if not usenone:
                    raise error.RepresentError(
                        "%s could not be represented as JSON object."%res)
        except TypeError:
            pass
        
        return resjson

class _FakeMgr(sbak.base.Object):
    " A fake global resource manager to be used before the real one is ready. "
    def __init__(self):
        pass
    def __getattr__(self,attr,val=None):
        raise error.ResourceError("Global resource manager not initialized.")
    __setattr__ = __getattr__



# ~~~~~~~~ FUNCTIONS ~~~~~~~~

def init():
    """ Initializes the global resource manager (GRM).
    
        This will create a Mgr object and attach loaders to it for all
        the basic types of resource. Nothing that relies on the GRM
        will function correctly (or at all) until this function is
        called.
        
        This is called by sbak.init(). Also, it is safe to call
        multiple times.
    """
    global _init, _mgr
    # Prevent multiple initilizations
    if not _init:
        _init = True
        # Intitialize a GRM for all basic resource types
        _mgr = ResourceManager()
        set_mgr(_mgr)
        _mgr.set_basics()

def set_mgr(mgrObj):
    """ Change to a different global resource manager.
    
        *DEPRICATED* - Do not use.
        
        This will replace the ResourceManager object being used as the
        GRM by a different one. Also, it will import all the Mgr
        object's method into the module's scope for fast access.
    """
    global _mgr, set_ext_tag, remove_ext_tag, set_tag_loader,\
           remove_tag_loader, get_cwd, set_cwd, set_tag_dir, get,\
           set_metadata, truepath, get_src
    
    # Set the global mgr 
    _mgr = mgrObj
    
    # Alias all methods
    set_ext_tag = _mgr.set_ext_tag
    remove_ext_tag = _mgr.remove_ext_tag
    set_tag_loader = _mgr.set_tag_loader
    remove_tag_loader = _mgr.remove_tag_loader
    get_cwd = _mgr.get_cwd
    set_cwd = _mgr.set_cwd
    set_tag_dir = _mgr.set_tag_dir
    get = _mgr.get
    set_metadata = _mgr.set_metadata
    truepath = _mgr.truepath
    get_src = _mgr.get_src

def get_mgr():
    """ Return the current global resource manager.
    
        *DEPRICATED* - Do not use.
    
        This returns a reference to the Mgr object currently acting as
        the GRM.
    """
    warnings.warn("Use of the GRM is deprecated - see sbak.resman.",
                  DeprecationWarning)
    return _mgr

# ~~~~~~ DEFAULT VALUES ~~~~~~

_init = False
_mgr = _FakeMgr()